package huffman;

import java.io.*;
import java.util.*;
import java.util.Map.*;

public class compression {

	private FileInputStream fis;
	private FileOutputStream fos;
	// private ObjectOutputStream oos;

	private huffman huff;
	private HashMap<Byte, String> huffman_code;
	private HashMap<Byte, Integer> huffman_tree;
	private int buffer;
	private int count;

	public compression() {
		this.count = 0;
		this.buffer = 0;
	}

	public void write_int(int num) {
		try {
			byte[] byte_array = { 0, 0, 0, 0 };
			for (int i = 0; i != 32; ++i) {
				byte_array[i / 8] |= (((num >> i) & 1) << (i % 8));
			}
			for (int i = 3; i != -1; --i) {
				this.fos.write(byte_array[i]);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void to_codes(String path) {
		try {
			this.fos = new FileOutputStream(path + ".huf");
			// this.oos=new ObjectOutputStream(this.fos);
		} catch (Exception e) {
			e.printStackTrace();
			// System.exit(1);
		}

		try {
			// this.oos.writeInt(this.huffman_tree.size());
			this.write_int(this.huffman_tree.size());
			//System.out.println(this.huffman_tree.size());
			Iterator<Entry<Byte, Integer>> iter = this.huffman_tree.entrySet()
					.iterator();
			while (iter.hasNext()) {
				Entry<Byte, Integer> e = iter.next();
				this.fos.write(e.getKey());
				this.write_int(e.getValue());
				// this.oos.writeByte(e.getKey());
				// this.oos.writeInt(e.getValue());
				//System.out.println(e.getKey());
				//System.out.println(e.getValue());
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		byte[] byte_array = new byte[256];
		int len = -1;
		try {
			this.fis = new FileInputStream(path);
		} catch (Exception e) {
			e.printStackTrace();
			// System.exit(1);
		}

		try {
			while ((len = this.fis.read(byte_array)) != -1) {
				for (int i = 0; i != len; ++i) {
					String code = this.huffman_code.get(byte_array[i]);
					for (int j = 0; j != code.length(); ++j) {
						if (code.charAt(j) == '1')
							this.writeBit(true);
						else
							this.writeBit(false);
						// System.out.print(code.charAt(j));
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			// System.exit(1);
		}

		close();
	}

	public void writeBit(boolean bit) throws Exception {
		if (bit)
			writeBit(1);
		else
			writeBit(0);
	}

	public void close() {
		try {
			fos.flush();
			if (count != 0) {
				int left_bits = 8 - count;
				buffer = buffer << left_bits;
				fos.write(buffer);
				fos.write(left_bits);
			} else
				fos.write(0);
			fis.close();
			fos.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void writeBit(int bit) {
		try {
			if (bit < 0 || bit > 1)
				throw new Exception();
			count++;
			buffer = (buffer << 1);
			if (bit == 1)
				buffer |= 1;
			if (count == 8) {
				fos.write(buffer);
				count = 0;
				buffer = 0;
			}
		} catch (Exception e) {
			e.printStackTrace();
			// System.exit(1);
		}
	}

	public void compress(String path) {
		read_file read_f = new read_file();
		this.huffman_tree = read_f.read(path);
		this.huff = new huffman(this.huffman_tree);
		this.huffman_code = this.huff.get_huffman_code();
		// System.out.println(this.huff.print_codes());
		this.to_codes(path);
	}

	public void compress(File file) {
		String path = file.getAbsolutePath();
		// System.out.println(path);
		read_file read_f = new read_file();
		this.huffman_tree = read_f.read(path);
		this.huff = new huffman(this.huffman_tree);
		this.huffman_code = this.huff.get_huffman_code();
		// System.out.println(this.huff.print_codes());
		this.to_codes(path);
	}

	public static void main(String[] arg) {
		compression test = new compression();
		test.compress(new File("F:\\test\\test.txt"));
	}
}
